#ifndef __M_SESSION_HPP__
#define __M_SESSION_HPP__

#include <iostream>
#include <unordered_map>
#include <mutex>
#include <memory>
#include <websocketpp/server.hpp>
#include <websocketpp/config/asio_no_tls.hpp>
#include "util.hpp"
#include "logger.hpp"

typedef enum
{
    UNLOGIN,
    LOGIN
} ss_statu;

// 定义一个session类，对于session操作进行管理
class session
{
public:
    session(uint64_t ssid) : _ssid(ssid) { DLOG("session %p 被创建!!!", this); }
    ~session() { DLOG("session %p 被释放!!!", this); }

    /*获取ssid*/
    uint64_t ssid() { return _ssid; }
    /*设置session状态*/
    void set_statu(ss_statu statu) { _statu = statu; }
    /*设置用户ID*/
    void set_user(uint64_t uid) { _uid = uid; }
    /*获取用户ID*/
    uint64_t get_user() { return _uid; }
    /*判断是否登录*/
    bool is_login() { return _statu == LOGIN; }
    /*设置定时器*/
    void set_timer(const wsserver_t::timer_ptr &tp) { _tp = tp; }
    /*获取定时器*/
    wsserver_t::timer_ptr &get_timer() { return _tp; }

private:
    uint64_t _ssid;            // 标识ssid
    uint64_t _uid;             // 用户ID，一个用户对应一个有效的ssid
    ss_statu _statu;           // 标识用户状态，登录/未登录
    wsserver_t::timer_ptr _tp; // websocketpp自带的定时器，用作于关联session定时器
};

#define SESSION_TIMEOUT 30000
#define SESSION_FOREVER -1
using session_ptr = std::shared_ptr<session>;
/*定义一个session_manager类，实现对于所有session类的统一调度管理，定义增删等动作*/
class session_manager
{
public:
    session_manager(wsserver_t *server) : _next_ssid(1), _server(server) { DLOG("session管理器初始化完毕!!!"); }
    ~session_manager() { DLOG("session 管理器释放!!!"); }

    /*创建session*/
    session_ptr create_session(uint64_t uid, ss_statu statu)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        session_ptr ssp(new session(_next_ssid));
        ssp->set_statu(statu);
        ssp->set_user(uid);
        _session.insert(std::make_pair(_next_ssid, ssp));
        _next_ssid++;
        return ssp;
    }

    /*添加session*/
    void append_session(const session_ptr &ssp)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _session.insert(std::make_pair(ssp->ssid(), ssp));
        return;
    }

    /*通过ssid获取session信息*/
    session_ptr get_session_by_ssid(uint64_t ssid)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        auto iter = _session.find(ssid);
        if (iter == _session.end())
            return session_ptr();
        return iter->second;
    }

    /*通过ssid移除session管理*/
    void remove_session(uint64_t ssid)
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _session.erase(ssid);
    }

    /*设置session过期时间(毫秒级别)*/
    void set_session_expire_time(uint64_t ssid, int ms)
    {
        // 依赖于websocketpp本身提供的定时器来完成session生命周期的管理工作
        // 1.获取session信息
        session_ptr ssp = get_session_by_ssid(ssid);
        if (ssp.get() == nullptr)
            return;
        wsserver_t::timer_ptr tp = ssp->get_timer();
        // 2.根据场景判断
        if (tp.get() == nullptr && ms == SESSION_FOREVER) // a.在session永久的情况下，将session设置为永久
        {
            return;
        }
        else if (tp.get() == nullptr && ms != SESSION_FOREVER) // b.在session永久的情况下，将session设置为指定时间后执行删除任务
        {
            wsserver_t::timer_ptr tmp_tp=_server->set_timer(ms,std::bind(&session_manager::remove_session,this,ssid));
            ssp->set_timer(tmp_tp);
            return;
        }
        else if(tp.get()!=nullptr&&ms==SESSION_FOREVER)//c.在session设置了定时删除任务下，将session更改为永久
        {
            //websocktpp中定时器的取消不是取消定时器，而是立即执行定时任务
            //所以在取消定时任务之后，要将sessio重新加入到_session中管理
            tp->cancel();//取消也不是立即取消，是等到服务器执行的时候才会取消
            ssp->set_timer(wsserver_t::timer_ptr());//将session关联的定时器设置为空
            _server->set_timer(0,std::bind(&session_manager::append_session,this,ssp));//利用websocketpp定时器的功能重新将session添加进_session中管理
        }
        else if(tp.get()!=nullptr&&ms!=SESSION_FOREVER)//d.在设置了session定时删除任务的情况下，重置session的删除时间
        {
            tp->cancel();//取消定时器
            ssp->set_timer(wsserver_t::timer_ptr());//将管理的定时器句柄置空
            _server->set_timer(0,std::bind(&session_manager::append_session,this,ssp));//利用websocketpp定时器的功能重新将session添加进_session中管理
            //重新设置定时删除任务
            wsserver_t::timer_ptr tmp_tp=_server->set_timer(ms,std::bind(&session_manager::remove_session,this,ssp->ssid()));
            ssp->set_timer(tmp_tp);
        }
    }

private:
    uint64_t _next_ssid;                                // 以自增长方式分配的sessionID
    std::mutex _mutex;                                  // 加锁保护数据操作
    std::unordered_map<uint64_t, session_ptr> _session; // 建立sessionID和session的映射关系，统一管理session
    wsserver_t *_server;                                // websocket管理句柄
};

#endif